// addressing modes
const am = { abs : 1, abx : 2, abx_nc : 3, aby : 4, aby_nc : 5, acc : 6, imm : 7, imp : 8, ind : 9, inx : 10, iny : 11, iny_nc : 12, rel : 13, zep : 14, zpx : 15, zpy : 16, unk : 17 };

// flags
const flag = { n : 0x80, v : 0x40, p : 0x20, b : 0x10, d : 0x08, i : 0x04, z : 0x02, c : 0x01 };

// mirroring types
const mirroring = { horizontal : 1, vertical : 2 };

// nestest mode
const nestest_mode = true;

// debug mode
const debug_mode = false;

// opcode's names
const opcode_name = [
    //      0      1      2      3      4      5      6      7      8      9      A      B      C      D      E      F
    /* 0 */ "???", "ORA", "???", "???", "???", "ORA", "ASL", "???", "PHP", "ORA", "ASL", "???", "???", "ORA", "ASL", "???",
    /* 1 */ "BPL", "ORA", "???", "???", "???", "ORA", "ASL", "???", "CLC", "ORA", "???", "???", "???", "ORA", "ASL", "???",
    /* 2 */ "JSR", "AND", "???", "???", "BIT", "AND", "ROL", "???", "PLP", "AND", "ROL", "???", "BIT", "AND", "ROL", "???",
    /* 3 */ "BMI", "AND", "???", "???", "???", "AND", "ROL", "???", "SEC", "AND", "???", "???", "???", "AND", "ROL", "???",
    /* 4 */ "RTI", "EOR", "???", "???", "???", "EOR", "LSR", "???", "PHA", "EOR", "LSR", "???", "JMP", "EOR", "LSR", "???",
    /* 5 */ "BVC", "EOR", "???", "???", "???", "EOR", "LSR", "???", "???", "EOR", "???", "???", "???", "EOR", "LSR", "???",
    /* 6 */ "RTS", "ADC", "???", "???", "???", "ADC", "ROR", "???", "PLA", "ADC", "ROR", "???", "JMP", "ADC", "ROR", "???",
    /* 7 */ "BVS", "ADC", "???", "???", "???", "ADC", "ROR", "???", "SEI", "ADC", "???", "???", "???", "ADC", "ROR", "???",
    /* 8 */ "???", "STA", "???", "???", "STY", "STA", "STX", "???", "DEY", "???", "TXA", "???", "STY", "STA", "STX", "???",
    /* 9 */ "BCC", "STA", "???", "???", "STY", "STA", "STX", "???", "TYA", "STA", "TXS", "???", "???", "STA", "???", "???",
    /* A */ "LDY", "LDA", "LDX", "???", "LDY", "LDA", "LDX", "???", "TAY", "LDA", "TAX", "???", "LDY", "LDA", "LDX", "???",
    /* B */ "BCS", "LDA", "???", "???", "LDY", "LDA", "LDX", "???", "CLV", "LDA", "TSX", "???", "LDY", "LDA", "LDX", "???",
    /* C */ "CPY", "CMP", "???", "???", "CPY", "CMP", "DEC", "???", "INY", "CMP", "DEX", "???", "CPY", "CMP", "DEC", "???",
    /* D */ "BNE", "CMP", "???", "???", "???", "CMP", "DEC", "???", "CLD", "CMP", "???", "???", "???", "CMP", "DEC", "???",
    /* E */ "CPX", "SBC", "???", "???", "CPX", "SBC", "INC", "???", "INX", "SBC", "NOP", "???", "CPX", "SBC", "INC", "???",
    /* F */ "BEQ", "SBC", "???", "???", "???", "SBC", "INC", "???", "SED", "SBC", "???", "???", "???", "SBC", "INC", "???"
];

// opcode's addressing modes
const opcode_mode = [
    //      0       1       2       3       4       5       6       7       8       9          A       B       C       D          E          F
    /* 0 */ am.unk, am.inx, am.unk, am.unk, am.unk, am.zep, am.zep, am.unk, am.imp, am.imm,    am.acc, am.unk, am.unk, am.abs,    am.abs,    am.unk,
    /* 1 */ am.rel, am.iny, am.unk, am.unk, am.unk, am.zpx, am.zpx, am.unk, am.imp, am.aby,    am.unk, am.unk, am.unk, am.abx,    am.abx_nc, am.unk,
    /* 2 */ am.abs, am.inx, am.unk, am.unk, am.zep, am.zep, am.zep, am.unk, am.imp, am.imm,    am.acc, am.unk, am.abs, am.abs,    am.abs,    am.unk,
    /* 3 */ am.rel, am.iny, am.unk, am.unk, am.unk, am.zpx, am.zpx, am.unk, am.imp, am.aby,    am.unk, am.unk, am.unk, am.abx,    am.abx_nc, am.unk,
    /* 4 */ am.imp, am.inx, am.unk, am.unk, am.unk, am.zep, am.zep, am.unk, am.imp, am.imm,    am.acc, am.unk, am.abs, am.abs,    am.abs,    am.unk,
    /* 5 */ am.rel, am.iny, am.unk, am.unk, am.unk, am.zpx, am.zpx, am.unk, am.imp, am.aby,    am.unk, am.unk, am.unk, am.abx,    am.abx_nc, am.unk,
    /* 6 */ am.imp, am.inx, am.unk, am.unk, am.unk, am.zep, am.zep, am.unk, am.imp, am.imm,    am.acc, am.unk, am.ind, am.abs,    am.abs,    am.unk,
    /* 7 */ am.rel, am.iny, am.unk, am.unk, am.unk, am.zpx, am.zpx, am.unk, am.imp, am.aby,    am.unk, am.unk, am.unk, am.abx,    am.abx_nc, am.unk,
    /* 8 */ am.unk, am.inx, am.unk, am.unk, am.zep, am.zep, am.zep, am.unk, am.imp, am.unk,    am.imp, am.unk, am.abs, am.abs,    am.abs,    am.unk,
    /* 9 */ am.rel, am.iny, am.unk, am.unk, am.zpx, am.zpx, am.zpy, am.unk, am.imp, am.aby_nc, am.imp, am.unk, am.unk, am.abx_nc, am.unk,    am.unk,
    /* A */ am.imm, am.inx, am.imm, am.unk, am.zep, am.zep, am.zep, am.unk, am.imp, am.imm,    am.imp, am.unk, am.abs, am.abs,    am.abs,    am.unk,
    /* B */ am.rel, am.iny, am.unk, am.unk, am.zpx, am.zpx, am.zpy, am.unk, am.imp, am.aby,    am.imp, am.unk, am.abx, am.abx,    am.aby,    am.unk,
    /* C */ am.imm, am.inx, am.unk, am.unk, am.zep, am.zep, am.zep, am.unk, am.imp, am.imm,    am.imp, am.unk, am.abs, am.abs,    am.abs,    am.unk,
    /* D */ am.rel, am.iny, am.unk, am.unk, am.unk, am.zpx, am.zpx, am.unk, am.imp, am.aby,    am.unk, am.unk, am.unk, am.abx,    am.abx_nc, am.unk,
    /* E */ am.imm, am.inx, am.unk, am.unk, am.zep, am.zep, am.zep, am.unk, am.imp, am.imm,    am.imp, am.unk, am.abs, am.abs,    am.abs,    am.unk,
    /* F */ am.rel, am.iny, am.unk, am.unk, am.unk, am.zpx, am.zpx, am.unk, am.imp, am.aby,    am.unk, am.unk, am.unk, am.abx,    am.abx_nc, am.unk
];

// opcode's cycles
const opcode_cycles = [
    //      0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
    /* 0 */ 0, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 0, 4, 6, 0,
    /* 1 */ 2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
    /* 2 */ 6, 6, 0, 0, 3, 3, 5, 0, 4, 2, 2, 0, 4, 4, 6, 0,
    /* 3 */ 2, 5, 2, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
    /* 4 */ 6, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 3, 4, 6, 0,
    /* 5 */ 2, 5, 0, 0, 0, 4, 6, 0, 0, 4, 0, 0, 0, 4, 7, 0,
    /* 6 */ 6, 6, 0, 0, 0, 3, 5, 0, 4, 2, 2, 0, 5, 4, 6, 0,
    /* 7 */ 2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
    /* 8 */ 0, 6, 0, 0, 3, 3, 3, 0, 2, 0, 2, 0, 4, 4, 4, 0,
    /* 9 */ 2, 6, 0, 0, 4, 4, 4, 0, 2, 5, 2, 0, 0, 5, 0, 0,
    /* A */ 2, 6, 2, 0, 3, 3, 3, 0, 2, 2, 2, 0, 4, 4, 4, 0,
    /* B */ 2, 5, 0, 0, 4, 4, 4, 0, 2, 4, 2, 0, 4, 4, 4, 0,
    /* C */ 2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0,
    /* D */ 2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
    /* E */ 2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0,
    /* F */ 2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0
];

// palette
const palette = [
    0x757575, 0x271B8F, 0x0000AB, 0x47009F, 0x8F0077, 0xAB0013, 0xA70000, 0x7F0B00,
    0x432F00, 0x004700, 0x005100, 0x003F17, 0x1B3F5F, 0x000000, 0x000000, 0x000000,
    0xBCBCBC, 0x0073EF, 0x233BEF, 0x8300F3, 0xBF00BF, 0xE7005B, 0xDB2B00, 0xCB4F0F,
    0x8B7300, 0x009700, 0x00AB00, 0x00933B, 0x00838B, 0x000000, 0x000000, 0x000000,
    0xFFFFFF, 0x3FBFFF, 0x5F97FF, 0xA78BFD, 0xF77BFF, 0xFF77B7, 0xFF7763, 0xFF9B3B,
    0xF3BF3F, 0x83D313, 0x4FDF4B, 0x58F898, 0x00EBDB, 0x000000, 0x000000, 0x000000,
    0xFFFFFF, 0xABE7FF, 0xC7D7FF, 0xD7CBFF, 0xFFC7FF, 0xFFC7DB, 0xFFBFB3, 0xFFDBAB,
    0xFFE7A3, 0xE3FFA3, 0xABF3BF, 0xB3FFCF, 0x9FFFF3, 0x000000, 0x000000, 0x000000
];

NEStype = Class.create({

    initialize : function( args ) {
        Object.extend( this, args );
        this.rom_data = [];
        args.rom_data.evalJSON().each( function(i) {
            this.rom_data.push( parseInt(i, 16) );
        }, this );
        this.memory = new NEStype.Memory();
        this.cart = new NEStype.Cartridge({ name : this.rom_name, data : this.rom_data });
        this.debugger = new NEStype.Debugger({ log_target : this.target });
        this.m6502 = new NEStype.M6502({ context : this, debugger : this.debugger });
        this.ppu = new NEStype.PPU({ context : this });
        this.cart.load_cart( this.memory.get_ram(), this.memory.get_vram() );
        this.m6502.reset();
        this.run();
    },

    run : function() {
        for( var i = 0; i < 6000; i++ ) {
            var opcode = this.m6502.fetch();
            this.m6502.execute( opcode );
        }
    },

    read_memory : function( address ) {
        switch( address ) {
            case 0x2002: return this.ppu.read_2002();
            case 0x2004: return this.ppu.read_2004();
            case 0x2007: var t = this.ppu.read_2007( address ); this.ppu.increment_vram(); return t;
            default: return this.memory.read( address );
        }
    },

    write_memory : function( address, value ) {
        switch( address ) {
            case 0x2000: this.ppu.write_2000( value ); return;
            case 0x2001: this.ppu.write_2001( value ); return;
            case 0x2003: this.ppu.write_2003( value ); return;
            case 0x2004: this.ppu.write_2004( value ); return;
            case 0x2005: this.ppu.write_2005( value ); return;
            case 0x2006: this.ppu.write_2006( value ); return;
            case 0x2007: this.ppu.write_2007( value ); this.ppu.increment_vram(); return;
            default: this.memory.write( address, value ); return;
        }
    },

    read_sram : function( address ) {
        return this.memory.read_sram( address );
    },

    read_vram : function( address ) {
        return this.memory.read_vram( address );
    },

    write_sram : function( address, value ) {
        this.memory.write_sram( address, value );
    },

    write_vram : function( address, value ) {
        this.memory.write_vram( address, value );
    },

    handle_nmi : function() {
        this.m6502.handle_nmi();
    }

});

NEStype.Cartridge = Class.create({

    initialize : function( args ) {
        Object.extend( this, args );
    },

    load_cart : function( ram, vram ) {
        var rom_banks = this.data[4];
        var offset = 16;
        if( rom_banks == 1 ) {
            for( var i = 0; i < 0x4000; i++ ) {
                ram[0x8000 + i] = this.data[i + 16];
                ram[0xc000 + i] = this.data[i + 16];
                offset++;
            }
        } else {
            for( var i = 0; i < 0x8000; i++ ) {
                ram[0x8000 + i] = this.data[i + 16];
                offset++;
            }
        }
        for( var i = 0; i < 0x2000; i++ ) {
            vram[0x8000 + i] = this.data[offset + i];
        }
    }

});

NEStype.Memory = Class.create({

    initialize : function( args ) {
        Object.extend( this, args );
        this.ram = [];
        for( var i = 0; i < 0x10000; i++ ) this.ram.push( 0 );
        this.vram = [];
        for( var i = 0; i < 0x4000; i++ ) this.ram.push( 0 );
        this.sram = [];
        for( var i = 0; i < 0x100; i++ ) this.ram.push( 0 );
    },

    read : function( address ) {
        return this.ram[address];
    },

    write : function( address, value ) {
        this.ram[address] = value;
    },

    read_sram : function( address ) {
        return this.sram[address];
    },

    read_vram : function( address ) {
        return this.vram[address];
    },

    write_sram : function( address, value ) {
        this.sram[address] = value;
    },

    write_vram : function( address, value ) {
        this.vram[address] = value;
    },

    get_ram : function() {
        return this.ram;
    },

    get_vram : function() {
        return this.vram;
    }

});

NEStype.PPU = Class.create({

    initialize : function( args ) {
        this.context = null;
        this.sprite_overflow = false;
        this.sprite_zero_hit = false;
        this.in_vblank = false;
        this.vram_latch = false;
        this.sram_address = 0;
        this.vram_buffer = 0;
        this.vram_address = 0;
        this.vram_address_increment = 0;
        this.pattern_table_address = 0;
        this.nmi_on_vblank = false;
        this.loopy_t = 0;
        this.loopy_v = 0;
        this.background_enabled = false;
        this.mirroring = mirroring.horizontal; // FIXME
        this.scanline = 0;
        this.can_render = false;
        Object.extend( this, args );
    },

    render_scanline : function() {
        // pre-render scanline
        if( this.scanline == 0 ) {
            if( this.background_enabled ) this.loopy_v = this.loopy_t;
        }

        // rendered scanlines
        else if( this.scanline <= 240 ) {
            if( this.background_enabled ) {
                this.loopy_v = ( this.loopy_v & ~0x041f );
                this.loopy_v |= ( this.loopy_t & 0x041f );
            }

            this.sprite_overflow = false;
            this.sprite_zero_hit = false;

            if( this.background_enabled ) this.render_background();

            if( (this.loopy_v & 0x7000) == 0x7000 ) {
                var t = ( this.loopy_v & 0x03e0 );
                this.loopy_v &= 0x0fff;
                switch( t ) {
                    case 0x03a0: this.loopy_v ^= 0x0ba0; break;
                    case 0x03e0: this.loopy_v ^= 0x03e0; break;
                    default: this.loopy_v = ( (loopy_v + 0x20) & 0xffff ); break;
                }
            } else this.loopy_v = ( (this.loopy_v + 0x10000) & 0xffff );
        }

        // idle scanline
        else if( this.scanline == 241 ) {
            if( this.nmi_on_vblank ) this.contex.handle_nmi();
        }

        // vblanks scanlines
        else if( this.scanline == 242 ) {
            this.can_render = true;
            this.sram_address = 0;
            this.in_vblank = true;
        }

        // end of vblank scanlines
        else if( this.scanline == 262 ) {
            this.in_vblank = false;
            this.scanline = -1;
        }

        this.scanline++;
    },

    read_2002 : function() {
        var t = 0;
        if( this.sprite_overflow ) t |= 0x20;
        if( this.sprite_zero_hit ) t |= 0x40;
        if( this.in_vblank ) t |= 0x80;
        this.vram_latch = false;
        this.in_vblank = false;

        return t;
    },

    read_2004 : function() {
        return this.read_sram( this.sram_address );
    },

    read_2007 : function( address ) {
        var t = 0;

        if( address < 0x3f00 ) {
            t = this.vram_buffer;

            if( this.vram_address < 0x2000 ) {
                // TODO: mappers
            } else if( this.vram_address >= 0x2000 ) {
                this.vram_buffer = this.read_vram( this.vram_address & 0x3fff );
            }
        } else {
            t = this.read_vram( this.vram_address & 0x3fff );
        }

        return r;
    },

    write_2000 : function( value ) {
        this.vram_address_increment = ( (value & 0x4) == 0x4 ? 32 : 1 );
        this.pattern_table_address = ( (value & 0x10) == 0x10 ? 0x1000 : 0 );
        this.nmi_on_vblank = ( (value & 0x80) == 0x80 );
        this.loopy_t = ( loopy_t & ~0x0c00 );
        this.loopy_t |= ( (value & 0x3) << 10 );
    },

    write_2001 : function( value ) {
        this.background_enabled = ( (value & 0x8) == 0x8 );
    },

    write_2003 : function( value ) {
        this.sram_address = value;
    },

    write_2004 : function( value ) {
        this.write_sram( this.sram_address++, value );
        this.sram_address &= 0xff;
    },

    write_2005 : function( value ) {
        if( !this.vram_latch ) {
            this.loopy_t = ( this.loopy_t & ~0x001f );
            this.loopy_t |= ( value >> 3 );
        } else {
            this.loopy_t = ( this.loopy_t & ~0x73e0 );
            this.loopy_t |= ( (value & 0xf8) << 2 );
            this.loopy_t |= ( (value & 0x07) << 12 );
        }

        this.vram_latch ^= true;
    },

    write_2006 : function( value ) {
        if( !this.vram_latch ) {
            this.loopy_t = ( this.loopy_t & ~0xff00 );
            this.loopy_t |= ( (value & 0x3f) << 8 );
        } else {
            this.loopy_t = ( this.loopy_t & ~0x00ff );
            this.loopy_t |= value;
            this.vram_address = this.loopy_t;
            this.loopy_v = this.loopy_t;
        }

        this.vram_latch ^= true;
    },

    write_2007 : function( value ) {
        if( this.vram_address < 0x2000) {
            // TODO: mappers
        } else if( (this.vram_address >= 0x2000) && (this.vram_address < 0x3f00) ) {
            if( this.mirroring == mirroring.horizontal ) {
                switch( this.vram_address & 0x2c00 ) {
                    // FIXME
                    case 0x2000: this.write_vram( this.vram_address & 0x3fff, value ); this.write_vram( (this.vram_address & 0x3fff) + 0x0400, value ); break;
                    case 0x2400: this.write_vram( this.vram_address & 0x3fff, value ); this.write_vram( (this.vram_address & 0x3fff) - 0x0400, value ); break;
                    case 0x2800: this.write_vram( this.vram_address & 0x3fff, value ); this.write_vram( (this.vram_address & 0x3fff) + 0x0400, value ); break;
                    case 0x2c00: this.write_vram( this.vram_address & 0x3fff, value ); this.write_vram( (this.vram_address & 0x3fff) - 0x0400, value ); break;
                }
            } else if( this.mirroring == mirroring.vertical ) {
                switch( this.vram_address & 0x2c00 ) {
                    // FIXME
                    case 0x2000: this.write_vram( this.vram_address & 0x3fff, value ); this.write_vram( (this.vram_address & 0x3fff) + 0x0800, value ); break;
                    case 0x2400: this.write_vram( this.vram_address & 0x3fff, value ); this.write_vram( (this.vram_address & 0x3fff) + 0x0800, value ); break;
                    case 0x2800: this.write_vram( this.vram_address & 0x3fff, value ); this.write_vram( (this.vram_address & 0x3fff) + 0x0800, value ); break;
                    case 0x2c00: this.write_vram( this.vram_address & 0x3fff, value ); this.write_vram( (this.vram_address & 0x3fff) - 0x0800, value ); break;
                }
            }
        } else if( (this.vram_address >= 0x3f00) && (this.vram_address < 0x3f20) ) {
            this.write_vram( this.vram_address, value & 0x3f );
            if( (this.vram_address == 0x3f00) || (this.vram_address == 0x3f10) ) this.write_vram( 0x3f00, value & 0x3f ); this.write_vram( 0x3f10, value & 0x3f );
            if( (this.vram_address == 0x3f04) || (this.vram_address == 0x3f14) ) this.write_vram( 0x3f04, value & 0x3f ); this.write_vram( 0x3f14, value & 0x3f );
            if( (this.vram_address == 0x3f08) || (this.vram_address == 0x3f18) ) this.write_vram( 0x3f08, value & 0x3f ); this.write_vram( 0x3f18, value & 0x3f );
            if( (this.vram_address == 0x3f0c) || (this.vram_address == 0x3f1c) ) this.write_vram( 0x3f0c, value & 0x3f ); this.write_vram( 0x3f1c, value & 0x3f );
        }
    },

    read_sram : function( address ) {
        return this.context.read_sram( address );
    },

    read_vram : function( address ) {
        return this.context.read_vram( address );
    },

    write_sram : function( address, value ) {
        this.context.write_sram( address, value );
    },

    write_vram : function( address, value ) {
        this.context.write_vram( address, value );
    }

});

NEStype.M6502 = Class.create({

    initialize : function( args ) {
        this.a = 0;
        this.x = 0;
        this.y = 0;
        this.sp = 0;
        this.f = 0;
        this.cycles = 0;
        this.pc = 0;
        this.context = null;
        Object.extend( this, args );
    },

    reset : function() {
        this.a = 0;
        this.x = 0;
        this.y = 0;
        this.sp = 0xfd;
        this.f = 0x24;
        this.cycles = 0;
        if( !nestest_mode ) this.pc = ( this.read(0xfffd) << 8 ) | this.read( 0xfffc );
        else this.pc = 0xc000;
    },

    read : function( address ) {
        return this.context.read_memory( address );
    },

    write : function( address, value ) {
        return this.context.write_memory( address, value );
    },

    fetch : function() {
        var address = this.pc++;
        this.pc &= 0xffff;
        var opcode = this.read( address );

        return opcode;
    },

    execute : function( opcode ) {
        var myself = Object.clone( this );
        myself.pc = ( myself.pc - 1 ) & 0xffff;

        if( debug_mode ) {
            this.debugger.debug({
                context : myself,
                opcode  : opcode
            });
        }

        switch( opcode ) {
            case 0x01: this.opcode_ora( am.inx ); break;
            case 0x05: this.opcode_ora( am.zep ); break;
            case 0x06: this.opcode_asl( am.zep ); break;
            case 0x08: this.stack_push( this.f | 0x30 ); break;
            case 0x09: this.opcode_ora( am.imm ); break;
            case 0x0a: this.opcode_asl( am.acc ); break;
            case 0x0d: this.opcode_ora( am.abs ); break;
            case 0x0e: this.opcode_asl( am.abs ); break;
            case 0x10: this.pc = ( this.f & flag.n ) != flag.n ? this.solve_address( am.rel ) : this.pc + 1; this.pc &= 0xffff; break;
            case 0x11: this.opcode_ora( am.iny ); break;
            case 0x15: this.opcode_ora( am.zpx ); break;
            case 0x16: this.opcode_asl( am.zpx ); break;
            case 0x18: this.f &= ~flag.c; break;
            case 0x19: this.opcode_ora( am.aby ); break;
            case 0x1d: this.opcode_ora( am.abx ); break;
            case 0x1e: this.opcode_asl( am.abx_nc ); break;
            case 0x20: this.opcode_jsr( am.abs ); break;
            case 0x21: this.opcode_and( am.inx ); break;
            case 0x24: this.opcode_bit( am.zep ); break;
            case 0x25: this.opcode_and( am.zep ); break;
            case 0x26: this.opcode_rol( am.zep ); break;
            case 0x28: this.f = this.stack_pop(); this.f &= ~flag.b; this.f |= flag.p; break;
            case 0x29: this.opcode_and( am.imm ); break;
            case 0x2a: this.opcode_rol( am.acc ); break;
            case 0x2c: this.opcode_bit( am.abs ); break;
            case 0x2d: this.opcode_and( am.abs ); break;
            case 0x2e: this.opcode_rol( am.abs ); break;
            case 0x30: this.pc = ( this.f & flag.n ) == flag.n ? this.solve_address( am.rel ) : this.pc + 1; this.pc &= 0xffff; break;
            case 0x31: this.opcode_and( am.iny ); break;
            case 0x35: this.opcode_and( am.zpx ); break;
            case 0x36: this.opcode_rol( am.zpx ); break;
            case 0x38: this.f |= flag.c; break;
            case 0x39: this.opcode_and( am.aby ); break;
            case 0x3d: this.opcode_and( am.abx ); break;
            case 0x3e: this.opcode_rol( am.abx_nc ); break;
            case 0x40: this.opcode_rti(); break;
            case 0x41: this.opcode_eor( am.inx ); break;
            case 0x45: this.opcode_eor( am.zep ); break;
            case 0x46: this.opcode_lsr( am.zep ); break;
            case 0x48: this.stack_push( this.a ); break;
            case 0x49: this.opcode_eor( am.imm ); break;
            case 0x4a: this.opcode_lsr( am.acc ); break;
            case 0x4c: this.pc = this.solve_address( am.abs ); break;
            case 0x4d: this.opcode_eor( am.abs ); break;
            case 0x4e: this.opcode_lsr( am.abs ); break;
            case 0x50: this.pc = ( this.f & flag.v ) != flag.v ? this.solve_address( am.rel ) : this.pc + 1; this.pc &= 0xffff; break;
            case 0x51: this.opcode_eor( am.iny ); break;
            case 0x55: this.opcode_eor( am.zpx ); break;
            case 0x56: this.opcode_lsr( am.zpx ); break;
            case 0x59: this.opcode_eor( am.aby ); break;
            case 0x5d: this.opcode_eor( am.abx ); break;
            case 0x5e: this.opcode_lsr( am.abx_nc ); break;
            case 0x60: this.pc = this.pc = ( (this.stack_pop() | (this.stack_pop() << 8)) + 1 ) & 0xffff; break;
            case 0x61: this.opcode_adc( am.inx ); break;
            case 0x65: this.opcode_adc( am.zep ); break;
            case 0x66: this.opcode_ror( am.zep ); break;
            case 0x68: this.opcode_pla(); break;
            case 0x69: this.opcode_adc( am.imm ); break;
            case 0x6a: this.opcode_ror( am.acc ); break;
            case 0x6c: this.pc = this.solve_address( am.ind ); break;
            case 0x6d: this.opcode_adc( am.abs ); break;
            case 0x6e: this.opcode_ror( am.abs ); break;
            case 0x70: this.pc = ( this.f & flag.v ) == flag.v ? this.solve_address( am.rel ) : this.pc + 1; this.pc &= 0xffff; break;
            case 0x71: this.opcode_adc( am.iny ); break;
            case 0x75: this.opcode_adc( am.zpx ); break;
            case 0x76: this.opcode_ror( am.zpx ); break;
            case 0x78: this.f |= flag.i; break;
            case 0x79: this.opcode_adc( am.aby ); break;
            case 0x7d: this.opcode_adc( am.abx ); break;
            case 0x7e: this.opcode_ror( am.abx_nc ); break;
            case 0x81: this.opcode_sta( am.inx ); break;
            case 0x84: this.opcode_sty( am.zep ); break;
            case 0x85: this.opcode_sta( am.zep ); break;
            case 0x86: this.opcode_stx( am.zep ); break;
            case 0x88: this.opcode_dey(); break;
            case 0x8a: this.opcode_txa(); break;
            case 0x8c: this.opcode_sty( am.abs ); break;
            case 0x8d: this.opcode_sta( am.abs ); break;
            case 0x8e: this.opcode_stx( am.abs ); break;
            case 0x90: this.pc = ( this.f & flag.c ) != flag.c ? this.solve_address( am.rel ) : this.pc + 1; this.pc &= 0xffff; break;
            case 0x91: this.opcode_sta( am.iny ); break;
            case 0x94: this.opcode_sty( am.zpx ); break;
            case 0x95: this.opcode_sta( am.zpx ); break;
            case 0x96: this.opcode_stx( am.zpy ); break;
            case 0x98: this.opcode_tya(); break;
            case 0x99: this.opcode_sta( am.aby_nc ); break;
            case 0x9a: this.sp = this.x; break;
            case 0x9d: this.opcode_sta( am.abx_nc ); break;
            case 0xa0: this.opcode_ldy( am.imm ); break;
            case 0xa1: this.opcode_lda( am.inx ); break;
            case 0xa2: this.opcode_ldx( am.imm ); break;
            case 0xa4: this.opcode_ldy( am.zep ); break;
            case 0xa5: this.opcode_lda( am.zep ); break;
            case 0xa6: this.opcode_ldx( am.zep ); break;
            case 0xa8: this.opcode_tay(); break;
            case 0xa9: this.opcode_lda( am.imm ); break;
            case 0xaa: this.opcode_tax(); break;
            case 0xac: this.opcode_ldy( am.abs ); break;
            case 0xad: this.opcode_lda( am.abs ); break;
            case 0xae: this.opcode_ldx( am.abs ); break;
            case 0xb0: this.pc = ( this.f & flag.c ) == flag.c ? this.solve_address( am.rel ) : this.pc + 1; this.pc &= 0xffff; break;
            case 0xb1: this.opcode_lda( am.iny ); break;
            case 0xb4: this.opcode_ldy( am.zpx ); break;
            case 0xb5: this.opcode_lda( am.zpx ); break;
            case 0xb6: this.opcode_ldx( am.zpy ); break;
            case 0xb8: this.f &= ~flag.v; break;
            case 0xb9: this.opcode_lda( am.aby ); break;
            case 0xba: this.opcode_tsx(); break;
            case 0xbc: this.opcode_ldy( am.abx ); break;
            case 0xbd: this.opcode_lda( am.abx ); break;
            case 0xbe: this.opcode_ldx( am.aby ); break;
            case 0xc0: this.opcode_cpy( am.imm ); break;
            case 0xc1: this.opcode_cmp( am.inx ); break;
            case 0xc4: this.opcode_cpy( am.zep ); break;
            case 0xc5: this.opcode_cmp( am.zep ); break;
            case 0xc6: this.opcode_dec( am.zep ); break;
            case 0xc8: this.opcode_iny(); break;
            case 0xc9: this.opcode_cmp( am.imm ); break;
            case 0xca: this.opcode_dex(); break;
            case 0xcc: this.opcode_cpy( am.abs ); break;
            case 0xcd: this.opcode_cmp( am.abs ); break;
            case 0xce: this.opcode_dec( am.abs ); break;
            case 0xd0: this.pc = ( this.f & flag.z ) != flag.z ? this.solve_address( am.rel ) : this.pc + 1; this.pc &= 0xffff; break;
            case 0xd1: this.opcode_cmp( am.iny ); break;
            case 0xd5: this.opcode_cmp( am.zpx ); break;
            case 0xd6: this.opcode_dec( am.zpx ); break;
            case 0xd8: this.f &= ~flag.d; break;
            case 0xd9: this.opcode_cmp( am.aby ); break;
            case 0xdd: this.opcode_cmp( am.abx ); break;
            case 0xde: this.opcode_dec( am.abx_nc ); break;
            case 0xe0: this.opcode_cpx( am.imm ); break;
            case 0xe1: this.opcode_sbc( am.inx ); break;
            case 0xe4: this.opcode_cpx( am.zep ); break;
            case 0xe5: this.opcode_sbc( am.zep ); break;
            case 0xe6: this.opcode_inc( am.zep ); break;
            case 0xe8: this.opcode_inx(); break;
            case 0xe9: this.opcode_sbc( am.imm ); break;
            case 0xea: break;
            case 0xec: this.opcode_cpx( am.abs ); break;
            case 0xed: this.opcode_sbc( am.abs ); break;
            case 0xee: this.opcode_inc( am.abs ); break;
            case 0xf0: this.pc = ( this.f & flag.z ) == flag.z ? this.solve_address( am.rel ) : this.pc + 1; this.pc &= 0xffff; break;
            case 0xf1: this.opcode_sbc( am.iny ); break;
            case 0xf5: this.opcode_sbc( am.zpx ); break;
            case 0xf6: this.opcode_inc( am.zpx ); break;
            case 0xf8: this.f |= flag.d; break;
            case 0xf9: this.opcode_sbc( am.aby ); break;
            case 0xfd: this.opcode_sbc( am.abx ); break;
            case 0xfe: this.opcode_inc( am.abx_nc ); break;
            default: if( debug_mode ) this.debugger.release_buffer(); throw opcode.toString( 16 ) + ' invalid opcode';
        }

        var cycles = opcode_cycles[opcode];
        if( cycles == 0 ) throw opcode.toString( 16 ) + ' cycles error';
        this.cycles += cycles;

        return cycles;
    },

    handle_nmi : function() {
        this.stack_push( (this.pc >> 8) & 0xff );
        this.stack_push( this.pc & 0xff );
        this.stack_push( this.f | 0x20 );
        this.f |= flag.i;
        this.pc = ( this.read(0xfffa) | (this.read(0xfffb) << 8) );
    },

    stack_pop : function( value ) {
        return this.read( (++this.sp | 0x100) & 0xffff );
    },

    stack_push : function( value ) {
        this.write( (this.sp-- | 0x100) & 0xffff, value );
    },

    solve_address : function( mode ) {
        switch( mode ) {
            case am.abs: return this.mode_absolute();
            case am.abx: return this.mode_absolute_x();
            case am.abx_nc: return this.mode_absolute_x_no_cross();
            case am.aby: return this.mode_absolute_y();
            case am.aby_nc: return this.mode_absolute_y_no_cross();
            case am.acc: return this.pc;
            case am.imm: return this.mode_immediate();
            case am.ind: return this.mode_indirect();
            case am.inx: return this.mode_indirect_x();
            case am.iny: return this.mode_indirect_y();
            case am.rel: return this.mode_relative();
            case am.zep: return this.mode_zero_page();
            case am.zpx: return this.mode_zero_page_x();
            case am.zpy: return this.mode_zero_page_y();
            default: throw 'invalid addressing mode';
        }
    },

    mode_absolute : function() {
        var l = this.read( this.pc++ );
        this.pc &= 0xffff;
        var h = this.read( this.pc++ );
        this.pc &= 0xffff;

        return ( (h << 8) | l );
    },

    mode_absolute_x : function() {
        var l = this.read( this.pc++ );
        this.pc &= 0xffff;
        var h = this.read( this.pc++ );
        this.pc &= 0xffff;
        var address = ( (h << 8) | l );

        return ( address + this.x ) & 0xffff;
    },

    mode_absolute_x_no_cross : function() {
        var l = this.read( this.pc++ );
        this.pc &= 0xffff;
        var h = this.read( this.pc++ );
        this.pc &= 0xffff;
        var address = ( (h << 8) | l );

        return ( address + this.x ) & 0xffff;
    },

    mode_absolute_y : function() {
        var l = this.read( this.pc++ );
        this.pc &= 0xffff;
        var h = this.read( this.pc++ );
        this.pc &= 0xffff;
        var address = ( (h << 8) | l );

        return ( address + this.y ) & 0xffff;
    },

    mode_absolute_y_no_cross : function() {
        var l = this.read( this.pc++ );
        this.pc &= 0xffff;
        var h = this.read( this.pc++ );
        this.pc &= 0xffff;
        var address = ( (h << 8) | l );

        return ( address + this.y ) & 0xffff;
    },

    mode_immediate : function() {
        var address = this.pc++;
        this.pc &= 0xffff;

        return address;
    },

    mode_indirect : function() {
        var l = this.read( this.pc++ );
        this.pc &= 0xffff;
        var h = this.read( this.pc );
        var address1 = ( (h << 8) | l );
        var address2 = ( (h << 8) | ((l + 1) & 0xff) );
        l = this.read( address1 );
        h = this.read( address2 );

        return ( (h << 8) | l );
    },

    mode_indirect_x : function() {
        var l = ( this.read(this.pc) + this.x ) & 0xff;
        l = this.read( l );
        var h = ( this.read(this.pc++) + this.x + 1 ) & 0xff;
        h = this.read( h );
        this.pc &= 0xffff;

        return ( (h << 8) | l );
    },

    mode_indirect_y : function() {
        var l = this.read( this.pc );
        l = this.read( l );
        var h = ( this.read(this.pc++) + 1 ) & 0xff;
        h = this.read( h );
        this.pc &= 0xffff;
        var address = ( (h << 8) | l );

        return ( address + this.y ) & 0xffff;
    },

    mode_relative : function() {
        var before = ( (this.pc + 1) & 0xffff ) >> 8;
        var disp = this.read( this.pc++ );
        this.pc &= 0xffff;
        if( ((disp >> 7) & 0x1) == 0x1 ) disp -= 0x100;
        this.pc = ( this.pc + disp ) & 0xffff;

        return this.pc;
    },

    mode_zero_page : function() {
        var l = this.read( this.pc++ );
        this.pc &= 0xffff;

        return l;
    },

    mode_zero_page_x : function() {
        var l = ( this.read(this.pc++) + this.x ) & 0xff;
        this.pc &= 0xffff;

        return l;
    },

    mode_zero_page_y : function() {
        var l = ( this.read(this.pc++) + this.y ) & 0xff;
        this.pc &= 0xffff;

        return l;
    },

    opcode_adc : function( mode ) {
        var value = this.read( this.solve_address(mode) );
        var result = this.a + value + ( (this.f & flag.c) == flag.c ? 1 : 0 ) & 0xfffffff;
        this.f &= ~( flag.n | flag.z | flag.c | flag.v );
        if( result > 0x00ff ) this.f |= flag.c;
        if( !((((this.a ^ value) >> 7) & 0x1) == 0x1) && ((((this.a ^ (result & 0x00ff)) >> 7) & 0x1) == 0x1) ) this.f |= flag.v;
        this.a = ( result & 0x00ff );
        if( ((this.a >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( this.a == 0 ) this.f |= flag.z;
    },

    opcode_and : function( mode ) {
        this.a &= this.read( this.solve_address(mode) );
        this.f &= ~( flag.n | flag.z );
        if( ((this.a >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( this.a == 0 ) this.f |= flag.z;
    },

    opcode_asl : function( mode ) {
        var address = this.solve_address( mode );
        var value = mode != am.acc ? this.read( address ) : this.a;
        this.f &= ~( flag.n | flag.z | flag.c );
        if( ((value >> 7) & 0x1) == 0x1 ) this.f |= flag.c;
        value <<= 1;
        value &= 0xfe;
        if( ((value >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( value == 0 ) this.f |= flag.z;
        if( mode != am.acc ) this.write( address, value );
        else this.a = value;
    },

    opcode_bit : function( mode ) {
        var value = this.read( this.solve_address(mode) );
        this.f &= ~( flag.n | flag.z | flag.v );
        if( (this.a & value) == 0 ) this.f |= flag.z;
        if( ((value >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( ((value >> 6) & 0x1) == 0x1 ) this.f |= flag.v;
    },

    opcode_cmp : function( mode ) {
        var value = this.read( this.solve_address(mode) );
        var result = ( this.a - value ) & 0xff;
        this.f &= ~( flag.n | flag.z | flag.c );
        if( ((result >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( result == 0 ) this.f |= flag.z;
        if( this.a >= value ) this.f |= flag.c;
    },

    opcode_cpx : function( mode ) {
        var value = this.read( this.solve_address(mode) );
        var result = ( this.x - value ) & 0xff;
        this.f &= ~( flag.n | flag.z | flag.c );
        if( ((result >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( result == 0 ) this.f |= flag.z;
        if( this.x >= value ) this.f |= flag.c;
    },

    opcode_cpy : function( mode ) {
        var value = this.read( this.solve_address(mode) );
        var result = ( this.y - value ) & 0xff;
        this.f &= ~( flag.n | flag.z | flag.c );
        if( ((result >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( result == 0 ) this.f |= flag.z;
        if( this.y >= value ) this.f |= flag.c;
    },

    opcode_dec : function( mode ) {
        var address = this.solve_address( mode );
        var value = this.read( address );
        this.f &= ~( flag.n | flag.z );
        value = ( value - 1 ) & 0xff;
        if( ((value >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( value == 0 ) this.f |= flag.z;
        this.write( address, value );
    },

    opcode_dex : function() {
        this.x = ( this.x - 1 ) & 0xff;
        this.f &= ~( flag.n | flag.z );
        if( ((this.x >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( this.x == 0 ) this.f |= flag.z;
    },

    opcode_dey : function() {
        this.y = ( this.y - 1 ) & 0xff;
        this.f &= ~( flag.n | flag.z );
        if( ((this.y >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( this.y == 0 ) this.f |= flag.z;
    },

    opcode_eor : function( mode ) {
        this.a ^= this.read( this.solve_address(mode) );
        this.f &= ~( flag.n | flag.z );
        if( ((this.a >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( this.a == 0 ) this.f |= flag.z;
    },

    opcode_inc : function( mode ) {
        var address = this.solve_address( mode );
        var value = this.read( address );
        this.f &= ~( flag.n | flag.z );
        value = ( value + 1 ) & 0xff;
        if( ((value >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( value == 0 ) this.f |= flag.z;
        this.write( address, value );
    },

    opcode_inx : function() {
        this.x = ( this.x + 1 ) & 0xff;
        this.f &= ~( flag.n | flag.z );
        if( ((this.x >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( this.x == 0 ) this.f |= flag.z;
    },

    opcode_iny : function() {
        this.y = ( this.y + 1 ) & 0xff;
        this.f &= ~( flag.n | flag.z );
        if( ((this.y >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( this.y == 0 ) this.f |= flag.z;
    },

    opcode_jsr : function( mode ) {
        var pc = ( this.pc + 1 ) & 0xffff;
        this.stack_push( pc >> 8 );
        this.stack_push( pc & 0xff );
        this.pc = this.solve_address( mode );
    },

    opcode_lda : function( mode ) {
        this.a = this.read( this.solve_address(mode) );
        this.f &= ~( flag.z | flag.n );
        if( this.a == 0 ) this.f |= flag.z;
        if( ((this.a >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
    },

    opcode_ldx : function( mode ) {
        this.x = this.read( this.solve_address(mode) );
        this.f &= ~( flag.z | flag.n );
        if( this.x == 0 ) this.f |= flag.z;
        if( ((this.x >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
    },

    opcode_ldy : function( mode ) {
        this.y = this.read( this.solve_address(mode) );
        this.f &= ~( flag.z | flag.n );
        if( this.y == 0 ) this.f |= flag.z;
        if( ((this.y >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
    },

    opcode_lsr : function( mode ) {
        var address = this.solve_address( mode );
        var value = mode != am.acc ? this.read( address ) : this.a;
        this.f &= ~( flag.n | flag.z | flag.c );
        if( (value & 0x1) == 0x1 ) this.f |= flag.c;
        value >>= 1;
        value &= 0x7f;
        if( value == 0 ) this.f |= flag.z;
        if( mode != am.acc ) this.write( address, value );
        else this.a = value;
    },

    opcode_ora : function( mode ) {
        this.a |= this.read( this.solve_address(mode) );
        this.f &= ~( flag.n | flag.z );
        if( ((this.a >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( this.a == 0 ) this.f |= flag.z;
    },

    opcode_pla : function() {
        this.a = this.stack_pop();
        this.f &= ~( flag.z | flag.n );
        if( ((this.a >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( this.a == 0 ) this.f |= flag.z;
    },

    opcode_rol : function( mode ) {
        var address = this.solve_address( mode );
        var value = mode != am.acc ? this.read( address ) : this.a;
        var old_carry_set = ( this.f & flag.c ) == flag.c ? true : false;
        this.f &= ~( flag.n | flag.z | flag.c );
        if( ((value >> 7) & 0x1) == 0x1 ) this.f |= flag.c;
        value <<= 1;
        value &= 0xfe;
        if( old_carry_set ) value |= 0x01;
        if( ((value >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( value == 0 ) this.f |= flag.z;
        if( mode != am.acc ) this.write( address, value );
        else this.a = value;
    },

    opcode_ror : function( mode ) {
        var address = this.solve_address( mode );
        var value = mode != am.acc ? this.read( address ) : this.a;
        var old_carry_set = ( this.f & flag.c ) == flag.c ? true : false;
        this.f &= ~( flag.n | flag.z | flag.c );
        if( (value & 0x1) == 0x1 ) this.f |= flag.c;
        value >>= 1;
        value &= 0x7f;
        if( old_carry_set ) {
            value |= 0x80;
            this.f |= flag.n;
        }
        if( value == 0 ) this.f |= flag.z;
        if( mode != am.acc ) this.write( address, value );
        else this.a = value;
    },

    opcode_rti : function() {
        this.f = this.stack_pop();
        this.pc = this.stack_pop() | ( this.stack_pop() << 8 );
        this.f &= ~flag.b;
        this.f |= flag.p;
    },

    opcode_sbc : function( mode ) {
        var value = this.read( this.solve_address(mode) );
        var result = (this.a - value - ((this.f & flag.c) != flag.c ? 1 : 0) ) & 0xfffffff;
        this.f &= ~( flag.n | flag.z | flag.c | flag.v );
        if( result < 0x0100 ) this.f |= flag.c;
        if( ((((this.a ^ (result & 0x00ff)) >> 7) & 0x1) == 0x1) && ((((this.a ^ value) >> 7) & 0x1) == 0x1) ) this.f |= flag.v;
        this.a = ( result & 0x00ff );
        if( ((this.a >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( this.a == 0 ) this.f |= flag.z;
    },

    opcode_sta : function( mode ) {
        this.write( this.solve_address(mode), this.a );
    },

    opcode_stx : function( mode ) {
        this.write( this.solve_address(mode), this.x );
    },

    opcode_sty : function( mode ) {
        this.write( this.solve_address(mode), this.y );
    },

    opcode_tax : function() {
        this.x = this.a;
        this.f &= ~( flag.n | flag.z );
        if( ((this.x >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( this.x == 0 ) this.f |= flag.z;
    },

    opcode_tay : function() {
        this.y = this.a;
        this.f &= ~( flag.n | flag.z );
        if( ((this.y >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( this.y == 0 ) this.f |= flag.z;
    },

    opcode_tsx : function() {
        this.x = this.sp;
        this.f &= ~( flag.n | flag.z );
        if( ((this.x >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( this.x == 0 ) this.f |= flag.z;
    },

    opcode_txa : function() {
        this.a = this.x;
        this.f &= ~( flag.n | flag.z );
        if( ((this.a >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( this.a == 0 ) this.f |= flag.z;
    },

    opcode_tya : function() {
        this.a = this.y;
        this.f &= ~( flag.n | flag.z );
        if( ((this.a >> 7) & 0x1) == 0x1 ) this.f |= flag.n;
        if( this.a == 0 ) this.f |= flag.z;
    }

});

NEStype.Debugger = Class.create({

    initialize : function( args ) {
        Object.extend( this, args );
        this.buffer = [];
        this.cycles = 0;
        this.branch = false;
        this.page_cross = false;
        this.time_base = 3;
    },

    debug : function( args ) {
        var text = this.fix_string( args.context.pc, 4 ) + '  ' + this.fix_string( args.opcode, 2 ) + ' ';

        switch( opcode_mode[args.opcode] ) {
            case am.acc: text += this.mode_accumulator( args ); break;
            case am.abs: text += this.mode_absolute( args ); break;
            case am.abx: text += this.mode_absolute_x( args ); break;
            case am.abx_nc: text += this.mode_absolute_x_no_cross( args ); break;
            case am.aby: text += this.mode_absolute_y( args ); break;
            case am.aby_nc: text += this.mode_absolute_y_no_cross( args ); break;
            case am.imm: text += this.mode_immediate( args ); break;
            case am.imp: text += this.mode_implied( args ); break;
            case am.ind: text += this.mode_indirect( args ); break;
            case am.inx: text += this.mode_indirect_x( args ); break;
            case am.iny: text += this.mode_indirect_y( args ); break;
            case am.rel: text += this.mode_relative( args ); break;
            case am.zep: text += this.mode_zero_page( args ); break;
            case am.zpx: text += this.mode_zero_page_x( args ); break;
            case am.zpy: text += this.mode_zero_page_y( args ); break;
        }

        text += "a:" + this.fix_string( args.context.a, 2 );
        text += " x:" + this.fix_string( args.context.x, 2 );
        text += " y:" + this.fix_string( args.context.y, 2 );
        text += " p:" + this.fix_string( args.context.f, 2 );
        text += " sp:" + this.fix_string( args.context.sp, 2 );
        text += " cyc:" + this.fix_string( this.cycles, 3, ' ',  10 );

        this.cycles += opcode_cycles[args.opcode] * this.time_base;

        if( this.branch ) {
            this.cycles += this.time_base;
            this.branch = false;
        }

        if( this.page_cross ) {
            this.cycles += this.time_base;
            this.page_cross = false;
        }

        if( this.cycles >= 341 ) this.cycles -= 341;

        this.buffer.push( text.toUpperCase() );
    },

    release_buffer : function() {
        var text = '';
        
        this.buffer.each( function(i) {
            text += i + '\n';
        }, this );

        this.log_target.insert( new Element('pre').update(text) );
    },

    fix_string : function( input, size, character, base ) {
        var result = input.toString( base || 16 );

        while( result.length < size ) result = ( character || '0' ) + result;

        return result;
    },

    mode_absolute : function( args ) {
        var first = args.context.read( (args.context.pc + 1) & 0xffff );
        var second = args.context.read( (args.context.pc + 2) & 0xffff );

        var name = opcode_name[args.opcode];
        var str = '                       ';
        if( !(name == 'JMP' || name == 'JSR') ) {
            var address = ( second << 8 ) | first;
            str = ' = ' + this.fix_string( args.context.read(address), 2 ) + '                  ';
        }

        return this.fix_string( first, 2 ) + ' ' + this.fix_string( second, 2 ) + '  ' + name + ' $' + this.fix_string( second, 2 ) + this.fix_string( first, 2 ) + str;
    },

    mode_absolute_x : function( args ) {
        var first = args.context.read( (args.context.pc + 1) & 0xffff );
        var second = args.context.read( (args.context.pc + 2) & 0xffff );
        var address = ( (second << 8) | first );
        this.page_cross = ( second != (((address + args.context.x) & 0xffff) >> 8) & 0xff );

        return this.fix_string( first, 2 ) + ' ' + this.fix_string( second, 2 )  + '  ' + opcode_name[args.opcode] + ' $' + this.fix_string( address, 4 ) + ',X @ ' + this.fix_string( (address + args.context.x) & 0xffff, 4 ) + ' = ' + this.fix_string( args.context.read((address + args.context.x) & 0xffff), 2 ) + '         ';
    },

    mode_absolute_x_no_cross : function( args ) {
        var first = args.context.read( (args.context.pc + 1) & 0xffff );
        var second = args.context.read( (args.context.pc + 2) & 0xffff );
        var address = ( (second << 8) | first );

        return this.fix_string( first, 2 ) + ' ' + this.fix_string( second, 2 )  + '  ' + opcode_name[args.opcode] + ' $' + this.fix_string( address, 4 ) + ',X @ ' + this.fix_string( (address + args.context.x) & 0xffff, 4 ) + ' = ' + this.fix_string( args.context.read((address + args.context.x) & 0xffff), 2 ) + '         ';
    },

    mode_absolute_y : function( args ) {
        var first = args.context.read( (args.context.pc + 1) & 0xffff );
        var second = args.context.read( (args.context.pc + 2) & 0xffff );
        var address = ( (second << 8) | first );
        this.page_cross = ( second != (((address + args.context.y) & 0xffff) >> 8) & 0xff );

        return this.fix_string( first, 2 ) + ' ' + this.fix_string( second, 2 )  + '  ' + opcode_name[args.opcode] + ' $' + this.fix_string( address, 4 ) + ',Y @ ' + this.fix_string( (address + args.context.y) & 0xffff, 4 ) + ' = ' + this.fix_string( args.context.read((address + args.context.y) & 0xffff), 2 ) + '         ';
    },

    mode_absolute_y_no_cross : function( args ) {
        var first = args.context.read( (args.context.pc + 1) & 0xffff );
        var second = args.context.read( (args.context.pc + 2) & 0xffff );
        var address = ( (second << 8) | first );

        return this.fix_string( first, 2 ) + ' ' + this.fix_string( second, 2 )  + '  ' + opcode_name[args.opcode] + ' $' + this.fix_string( address, 4 ) + ',Y @ ' + this.fix_string( (address + args.context.y) & 0xffff, 4 ) + ' = ' + this.fix_string( args.context.read((address + args.context.y) & 0xffff), 2 ) + '         ';
    },

    mode_accumulator : function( args ) {
        return '       ' + opcode_name[args.opcode] + " A                           ";
    },

    mode_immediate : function( args ) {
        var first = args.context.read( (args.context.pc + 1) & 0xffff );

        return this.fix_string( first, 2 ) + '     ' + opcode_name[args.opcode] + ' #$' + this.fix_string( first, 2 ) + '                        ';
    },

    mode_implied : function( args ) {
        return '       ' + opcode_name[args.opcode] + '                             ';
    },

    mode_indirect : function( args ) {
        var l = args.context.read( (args.context.pc + 1) & 0xffff );
        var h = args.context.read( (args.context.pc + 2) & 0xffff );
        var address = ( (h << 8) | l );

        return this.fix_string( l, 2 ) + ' ' + this.fix_string( h, 2 ) + '  ' + opcode_name[args.opcode] + ' ($' + this.fix_string( address, 4 ) + ') = ' + this.fix_string( args.context.read((address + 1) & 0xffff), 2 ) + this.fix_string( args.context.read(address), 2 ) + '              ';
    },

    mode_indirect_x : function( args ) {
        var l = ( args.context.read((args.context.pc + 1) & 0xffff) + args.context.x ) & 0xff;
        l = args.context.read( l );
        var h = ( args.context.read((args.context.pc + 1) & 0xffff) + args.context.x + 1 ) & 0xff;
        h = args.context.read( h );
        var address = ( (h << 8) | l );
        var operand = args.context.read( (args.context.pc + 1) & 0xffff );

        return this.fix_string( operand, 2 )  + '     ' + opcode_name[args.opcode] + ' ($' + this.fix_string( operand, 2 ) + ',X) @ ' + this.fix_string( (operand + args.context.x) & 0xff, 2 ) + ' = ' + this.fix_string( address, 4 ) + ' = ' + this.fix_string( args.context.read(address), 2 ) + '    ';
    },

    mode_indirect_y : function( args ) {
        var l = args.context.read( (args.context.pc + 1) & 0xffff );
        l = args.context.read( l );
        var h = ( args.context.read((args.context.pc + 1) & 0xffff) + 1 ) & 0xff;
        h = args.context.read( h );
        var address = ( (h << 8) | l );
        var operand = args.context.read( (args.context.pc + 1) & 0xffff );
        this.page_cross = ( h != (((address + args.context.y) & 0xffff) >> 8) & 0xff );

        return this.fix_string( operand, 2 ) + '     ' + opcode_name[args.opcode] + ' ($' + this.fix_string( operand, 2 ) + '),Y = ' + this.fix_string( address, 4 ) + ' @ ' + this.fix_string( (address + args.context.y) & 0xffff, 4 ) + ' = ' + this.fix_string( args.context.read((address + args.context.y) & 0xffff), 2 ) + '  ';
    },

    mode_relative : function( args ) {
        var before = ( (args.context.pc + 2) & 0xffff ) >> 8;
        var disp = args.context.read( (args.context.pc + 1) & 0xffff );
        args.context.pc = ( args.context.pc + disp ) & 0xffff;
        args.context.pc = ( args.context.pc + 2 ) & 0xffff;

        switch( args.opcode ) {
            case 0x10: this.branch = ( (args.context.f & flag.n) != flag.n ); break;
            case 0x30: this.branch = ( (args.context.f & flag.n) == flag.n ); break;
            case 0x50: this.branch = ( (args.context.f & flag.v) != flag.v ); break;
            case 0x70: this.branch = ( (args.context.f & flag.v) == flag.v ); break;
            case 0x90: this.branch = ( (args.context.f & flag.c) != flag.c ); break;
            case 0xb0: this.branch = ( (args.context.f & flag.c) == flag.c ); break;
            case 0xd0: this.branch = ( (args.context.f & flag.z) != flag.z ); break;
            case 0xf0: this.branch = ( (args.context.f & flag.z) == flag.z ); break;
        }
        if( this.branch ) this.page_cross = ( before != (args.context.pc >> 8) & 0xff );

        return this.fix_string( disp & 0xff, 2 ) + '     ' + opcode_name[args.opcode] + ' $' + this.fix_string( args.context.pc, 4 ) + '                       ';
    },

    mode_zero_page : function( args ) {
        var address = args.context.read( (args.context.pc + 1) & 0xffff );
        var first = args.context.read( address );

        return this.fix_string( address, 2 ) + '     ' + opcode_name[args.opcode] + ' $' + this.fix_string( address, 2 ) + ' = ' + this.fix_string( first, 2 ) + '                    ';
    },

    mode_zero_page_x : function( args ) {
        var operand = args.context.read( (args.context.pc + 1) & 0xffff );

        return this.fix_string( operand, 2 ) + '     ' + opcode_name[args.opcode] + ' $' + this.fix_string( operand, 2 ) + ',X @ ' + this.fix_string( (operand + args.context.x) & 0xff, 2 ) + ' = ' + this.fix_string( args.context.read((operand + args.context.x) & 0xff), 2 ) + '             ';
    },

    mode_zero_page_y : function( args ) {
        var operand = args.context.read( (args.context.pc + 1) & 0xffff );

        return this.fix_string( operand, 2 ) + '     ' + opcode_name[args.opcode] + ' $' + this.fix_string( operand, 2 ) + ',Y @ ' + this.fix_string( (operand + args.context.y) & 0xff, 2 ) + ' = ' + this.fix_string( args.context.read((operand + args.context.y) & 0xff), 2 ) + '             ';
    }

});